/*
 * Licensed to the 南京汇海交通科技有限公司
 *
 * Copyright (c) 2025.
 * All rights reserved.
 *
 * 本软件属南京汇海交通科技有限公司所有，在未获得南京汇海交通科技有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.cje.gateway.security.handler;

import com.cje.gateway.security.config.ClientDetails;
import com.cje.gateway.security.config.CustomizeSecurityProperties;
import com.chframework.boot.core.response.status.ExceptionStatusEnum;
import com.chframework.common.constant.ApplicationConstant;
import com.chframework.common.exception.ApplicationException;
import com.chframework.common.exception.SystemInterrupterException;
import com.cje.toolkit.support.JwtKit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.util.AntPathMatcher;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

/**
 * 可参考 WebSessionServerSecurityContextRepository.java 的实现
 * <p>
 * 存储认证授权的相关信息
 * （1）白名单直接放行
 * （2）从请求头拿到令牌，根据令牌拿到用户数据
 * （3）通用用户数据进行认证，确认令牌是否有效
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DefaultSecurityContextRepository implements ServerSecurityContextRepository {

    private final CustomizeSecurityProperties properties;
    private final SessionAuthenticationManager sessionAuthenticationManager;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * AuthenticationWebFilter.onAuthenticationSuccess调用该方法
     */
    @Override
    public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {
        // Mono<SecurityContext> contextMono = ReactiveSecurityContextHolder.getContext();
        return Mono.empty();
    }

    /**
     * ReactorContextWebFilter.withSecurityContext调用该方法
     */
    @Override
    public Mono<SecurityContext> load(ServerWebExchange exchange) {
        AtomicReference<ServerHttpRequest> request = new AtomicReference<>(exchange.getRequest());
        final String logId = String.format("[%s]", request.get().getHeaders().getFirst(ApplicationConstant.RESPONSE_IDENTITY));

        String path = request.get().getPath().toString();
        log.info("{}请求路径 {} 的请求头原始数据是 {}", logId, path, request.get().getHeaders());
        // 白名单
        for (String url : properties.getWhiteList()) {
            if (antPathMatcher.match(url, path)) {
                log.info("{}请求路径 {} 在白名单中", logId, path);
                return Mono.empty();
            }
        }

        return readClientInfo(exchange)
                .doOnNext(this::validateClientInfo)
                .flatMap(clientDetails -> {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            clientDetails.getSessionPrincipal(),
                            clientDetails.getRequestHeaderToken());
                    authentication.setDetails(clientDetails);

                    return sessionAuthenticationManager.authenticate(authentication)
                            .map(SecurityContextImpl::new);
                })
                .cast(SecurityContext.class)
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
    }

    /**
     * 获取身份认证数据
     */
    private static Mono<ClientDetails> readClientInfo(ServerWebExchange serverWebExchange) {
        Function<ServerWebExchange, Mono<String>> readClientUserProfile = (ServerWebExchange exchange) ->
                Mono.just(Optional.ofNullable(exchange.getRequest()
                                .getHeaders()
                                .getFirst(ApplicationConstant.FLAG_MANAGED_PROFILE))
                        .orElse(StringUtils.EMPTY));

        // 获取HTTP请求日志链路标识
        String traceId = serverWebExchange.getAttribute(ApplicationConstant.RESPONSE_IDENTITY);
        // 尝试获取请求头中的token
        final String requestHeaderToken = JwtKit.getRequestHeaderToken(serverWebExchange.getRequest());
        // 获取HTTP请求的远程客户端设备标识
        final String clientIpAddress = serverWebExchange.getAttribute(ApplicationConstant.REMOTE_CLIENT_IP_KEY);

        return Mono.just(new ClientDetails())
                .flatMap(clientDetails -> {
                    clientDetails.setClientRequestId(traceId);
                    clientDetails.setRequestHeaderToken(requestHeaderToken);
                    clientDetails.setClientIpAddress(clientIpAddress);
                    return Mono.just(clientDetails);
                })
                .zipWith(readClientUserProfile.apply(serverWebExchange),
                        (clientDetails, flagManagedProfile) -> {
                            clientDetails.setClientUserProfile(flagManagedProfile);
                            return clientDetails;
                        })
                .zipWith(JwtKit.getLoginUser(requestHeaderToken), (clientDetails, loginUser) -> {
                    clientDetails.setSessionPrincipal(loginUser.getUsername());
                    clientDetails.setSessionClientIpAddress(null);
                    clientDetails.setSessionUserProfile(null);

                    return clientDetails;
                })
                .onErrorResume(e -> Mono.error(new ApplicationException(e)));
    }

    /**
     * token有效性验证
     */
    private void validateClientInfo(ClientDetails clientDetails) {
        if (StringUtils.isEmpty(clientDetails.getRequestHeaderToken())) {
            throw new SystemInterrupterException(ExceptionStatusEnum.WITHOUT_TOKEN_IN_HTTP_HEADER);
        } else if (StringUtils.isEmpty(clientDetails.getSessionPrincipal()) || StringUtils.isBlank(clientDetails.getSessionPrincipal())) {
            throw new SystemInterrupterException(ExceptionStatusEnum.EXPIRED_TOKEN_IN_HTTP_HEADER);
        } else {
            log.debug("[{}]终端显示层【{}】HTTP请求头的会话标识`{}`是`{}`", clientDetails.getClientRequestId(),
                    clientDetails.getClientIpAddress(), properties.getAuthTokenKey(), clientDetails.getRequestHeaderToken());
        }
        /*else if (!ClientDetails.validSessionId(clientDetails.getRequestHeaderToken())) {
            throw new SystemInterrupterException(ExceptionStatusEnum.INVALID_TOKEN_IN_HTTP_HEADER);
        }*/
    }

}
